package io.github.lianghengyuan.influxdb.service;


import io.github.lianghengyuan.influxdb.domain.WriteBaseEntity;
import org.influxdb.BatchOptions;
import org.influxdb.InfluxDB;
import org.influxdb.dto.BatchPoints;
import org.influxdb.dto.Point;
import org.influxdb.dto.Query;
import org.influxdb.dto.QueryResult;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

//FiXME-lhy 没有做统一的返回处理
//TODO-lhy 查询支持前端分页
//TODO-lhy 查询和写入方法增加数据库参数
@Component
@ConditionalOnProperty("spring.influx.url")
public class InfluxdbService {


    @Autowired
    private InfluxDB influxDB;

    private String defaultDatabase;

    private String defaultMeasurement;

    public static final TimeUnit UNIT = TimeUnit.SECONDS;

    private static final Logger log = LoggerFactory.getLogger(InfluxdbService.class);



    public void setDefaultDatabase(String defaultDataBase) {
        influxDB.setDatabase(defaultDataBase);
        this.defaultDatabase = defaultDataBase;
    }

    public String getDefaultDatabase() {
        return defaultDatabase;
    }

    public String getDefaultMeasurement() {
        return defaultMeasurement;
    }

    public void setDefaultMeasurement(String defaultMeasurement) {
        this.defaultMeasurement = defaultMeasurement;
    }

    //FIXME-lhy 放到不同服务的influxdbServiceConfig中，同defaultDatabase和defaultMeasurement
    @PostConstruct
    private void batchOption() {
        influxDB.enableBatch(
                BatchOptions.DEFAULTS
                        .jitterDuration(500)
        );
    }

    @PreDestroy
    private void free() {
        System.out.println("退出");
        influxDB.close();
    }


    public boolean createDataBase(String databaseName) {
        QueryResult queryResult = influxDB.query(new Query("CREATE DATABASE " + databaseName));
        return queryResult.hasError();
    }

    public List<QueryResult.Result> showDataBase() {
        QueryResult queryResult = influxDB.query(new Query("SHOW DATABASES"));
        return queryResult.getResults();
    }

    public boolean dropDataBase(String databaseName) {
        QueryResult queryResult = influxDB.query(new Query("DROP DATABASE " + databaseName));
        return queryResult.hasError();
    }

    //FIXME-lhy 方法名改为select
    @Deprecated
    public List<QueryResult.Result> selectAll() {
        QueryResult queryResult = influxDB.query(new Query("SELECT * FROM history"));
        return queryResult.getResults();
    }

    /**
     * influxdb可以在写入数据时创建数据库，但是保留策略需要预先定义好
     *
     *
     * @param tags
     * @param retentionPolicyName
     * @param fields
     * @param time
     * @param unit
     * @param database
     * @param measurement
     */

    public void write(Map<String, String> tags,Map<String, Object> fields, Long time, TimeUnit unit, String database,  String retentionPolicyName, String measurement) {
        Point point = Point.measurement(measurement).tag(tags).fields(fields).time(time, unit).build();
        influxDB.write(database, retentionPolicyName, point);
    }
    public void write(Map<String, String> tags, Map<String, Object> fields,String database,  String retentionPolicyName, String measurement) {
        Point point = Point.measurement(measurement).tag(tags).fields(fields).build();
        influxDB.write(database,retentionPolicyName,point);
    }
    public void write(Map<String, String> tags, Map<String, Object> fields, String measurement) {
        Point point = Point.measurement(measurement).tag(tags).fields(fields).build();
        influxDB.write(point);
    }
    public void write(Map<String, String> tags,Map<String, Object> fields,String measurement, Long time, TimeUnit unit) {
        Point point = Point.measurement(defaultMeasurement).tag(tags).fields(fields).time(time, unit).build();
        influxDB.write(point);
    }

    public void write(Map<String, String> tags,Map<String, Object> fields, Long time, TimeUnit unit) {
        Point point = Point.measurement(defaultMeasurement).tag(tags).fields(fields).time(time, unit).build();
        influxDB.write(point);
    }

    public void write(Map<String, String> tags,Map<String, Object> fields, Long time) {
        Point point = Point.measurement(defaultMeasurement).tag(tags).fields(fields).time(time, UNIT).build();
        influxDB.write(point);
    }

    public void write(Map<String, String> tags,Map<String, Object> fields) {
        Point point = Point.measurement(defaultMeasurement).tag(tags).fields(fields).build();
        influxDB.write(point);
    }

    public void write(WriteBaseEntity writeBase, TimeUnit unit) {
        if (StringUtils.hasLength(writeBase.getMeasurement())) {
            write(writeBase.getTags(),writeBase.getFields(), writeBase.getMeasurement(), writeBase.getTime(),unit);
        } else {
            write(writeBase.getTags(),writeBase.getFields(), writeBase.getTime(), unit);
        }
    }

    public void write(WriteBaseEntity writeBase) {
        write(writeBase, UNIT);
    }
//TODO 加保留策略参数的函数实现
    public void write(List<WriteBaseEntity> writeBaseEntityList, String database, TimeUnit unit) {
        BatchPoints batchPoints = BatchPoints.database(database).build();
        writeBaseEntityList.forEach(writeBaseEntity -> {
            Point point = getPoint(unit, writeBaseEntity);
            batchPoints.point(point);
        });
        influxDB.write(batchPoints);
    }

    public void write(List<WriteBaseEntity> writeBaseEntityList, String database) {
        write(writeBaseEntityList,database,UNIT);
    }
    public void write(List<WriteBaseEntity> writeBaseEntityList,TimeUnit unit) {
        BatchPoints batchPoints = BatchPoints.builder().build();
        writeBaseEntityList.forEach(writeBaseEntity -> {
            Point point = getPoint(unit, writeBaseEntity);
            batchPoints.point(point);
        });
        influxDB.write(batchPoints);
    }

    public void write(List<WriteBaseEntity> writeBaseEntityList) {
        write(writeBaseEntityList, UNIT);
    }

    @NotNull
    private Point getPoint(TimeUnit unit, WriteBaseEntity writeBaseEntity) {
        Point point = null;
        if (StringUtils.hasLength(writeBaseEntity.getMeasurement())) {
            point = Point.measurement(writeBaseEntity.getMeasurement()).tag(writeBaseEntity.getTags()).fields(writeBaseEntity.getFields()).time(writeBaseEntity.getTime(), unit).build();
        } else {
            point = Point.measurement(defaultMeasurement).tag(writeBaseEntity.getTags()).fields(writeBaseEntity.getFields()).time(writeBaseEntity.getTime(), unit).build();
        }
        return point;
    }

    public List<Map<String, Object>> select(String sql,String database) {
        QueryResult queryResult = influxDB.query(new Query(sql,database));
        return convertResultToListMap(queryResult);
    }

    public List<Map<String,Object>> select(Query query) {
        QueryResult queryResult = influxDB.query(query);
        return convertResultToListMap(queryResult);
    }

    private static List<Map<String, Object>> convertResultToListMap(QueryResult queryResult) {
        List<Map<String, Object>> resultList = new ArrayList<>();

        for (QueryResult.Result result : queryResult.getResults()) {
            if (result.getSeries() != null) {
                for (QueryResult.Series series : result.getSeries()) {
                    List<String> columns = series.getColumns();
                    List<List<Object>> values = series.getValues();

                    for (List<Object> valueList : values) {
                        Map<String, Object> map = new HashMap<>();
                        for (int i = 0; i < columns.size(); i++) {
                            map.put(columns.get(i), valueList.get(i));
                        }
                        resultList.add(map);
                    }
                }
            }
        }
        return resultList;
    }
}
